package com.grid.camera.pelco;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;

/** Pelco D TCP客户端 通过TCP连接发送Pelco D协议命令到摄像机 */
public class PelcoDTcpClient implements AutoCloseable {
    private final String host;
    private final int port;
    private int deviceAddress;
    private Socket socket;
    private OutputStream outputStream;
    private InputStream inputStream;
    private boolean connected = false;
    private int connectionTimeout = 5000; // 连接超时时间，默认5秒
    private int readTimeout = 3000; // 读取超时时间，默认3秒
    private int maxRetries = 3; // 最大重试次数，默认3次
    private int retryDelay = 500; // 重试延迟时间，默认500毫秒

    /**
     * 构造函数
     *
     * @param host 摄像机IP地址
     * @param port TCP端口
     * @param deviceAddress 设备地址 (1-255)
     */
    public PelcoDTcpClient(String host, int port, int deviceAddress) {
        this.host = host;
        this.port = port;
        this.deviceAddress = deviceAddress;
    }

    /**
     * 清除预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean clearPreset(int presetNumber) throws IOException {
        return sendCommand(PelcoDProtocol.clearPreset(deviceAddress, presetNumber));
    }

    /** 断开连接 */
    @Override
    public void close() {
        connected = false;

        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                // 忽略关闭异常
            }
            inputStream = null;
        }

        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                // 忽略关闭异常
            }
            outputStream = null;
        }

        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                // 忽略关闭异常
            }
            socket = null;
        }
    }

    /**
     * 连接到摄像机
     *
     * @return 是否连接成功
     * @throws IOException 连接异常
     */
    public boolean connect() throws IOException {
        if (connected) {
            return true;
        }

        try {
            socket = new Socket();
            socket.connect(new InetSocketAddress(host, port), connectionTimeout);
            socket.setSoTimeout(readTimeout); // 设置读取超时
            outputStream = socket.getOutputStream();
            inputStream = socket.getInputStream();
            connected = true;
            return true;
        } catch (IOException e) {
            close();
            throw e;
        }
    }

    /**
     * 获取设备地址
     *
     * @return 设备地址
     */
    public int getDeviceAddress() {
        return deviceAddress;
    }

    /**
     * 设置设备地址
     *
     * @param deviceAddress 设备地址 (1-255)
     */
    public void setDeviceAddress(int deviceAddress) {
        this.deviceAddress = deviceAddress;
    }

    /**
     * 调用预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean gotoPreset(int presetNumber) throws IOException {
        return sendCommand(PelcoDProtocol.gotoPreset(deviceAddress, presetNumber));
    }

    /**
     * 检查是否已连接
     *
     * @return 是否已连接
     */
    public boolean isConnected() {
        return connected && socket != null && socket.isConnected() && !socket.isClosed();
    }

    /**
     * 云台下移
     *
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean moveDown() throws IOException {
        return sendCommand(PelcoDProtocol.moveDown(deviceAddress));
    }

    /**
     * 云台上移
     *
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean moveUp() throws IOException {
        return sendCommand(PelcoDProtocol.moveUp(deviceAddress));
    }

    /**
     * 接收数据
     *
     * @param timeout 超时时间（毫秒）
     * @return 接收到的数据字节数组，如果没有数据则返回null
     * @throws IOException 接收异常
     */
    public byte[] receiveData(int timeout) throws IOException {
        if (!isConnected()) {
            if (!connect()) {
                return null;
            }
        }

        try {
            // 临时保存当前的超时设置
            int originalTimeout = socket.getSoTimeout();

            try {
                // 设置新的超时
                socket.setSoTimeout(timeout);

                // 检查是否有数据可读
                if (inputStream.available() > 0 || waitForData(timeout)) {
                    // 创建缓冲区
                    byte[] buffer = new byte[1024];
                    int bytesRead = inputStream.read(buffer);

                    if (bytesRead > 0) {
                        // 返回实际读取的数据
                        return Arrays.copyOf(buffer, bytesRead);
                    }
                }

                return null;
            } finally {
                // 恢复原始超时设置
                socket.setSoTimeout(originalTimeout);
            }
        } catch (IOException e) {
            close();
            throw e;
        }
    }

    /**
     * 接收数据（使用默认超时时间）
     *
     * @return 接收到的数据字节数组，如果没有数据则返回null
     * @throws IOException 接收异常
     */
    public byte[] receiveData() throws IOException {
        return receiveData(readTimeout);
    }

    /**
     * 发送Pelco D命令
     *
     * @param command Pelco D命令数据包
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean sendCommand(byte[] command) throws IOException {
        int retries = 0;
        IOException lastException = null;

        while (retries <= maxRetries) {
            try {
                if (!isConnected()) {
                    if (!connect()) {
                        retries++;
                        if (retries <= maxRetries) {
                            try {
                                Thread.sleep(retryDelay);
                            } catch (InterruptedException ie) {
                                Thread.currentThread().interrupt();
                                throw new IOException("发送命令被中断", ie);
                            }
                            continue;
                        }
                        return false;
                    }
                }

                outputStream.write(command);
                outputStream.flush();
                return true;
            } catch (IOException e) {
                lastException = e;
                close();

                retries++;
                if (retries <= maxRetries) {
                    try {
                        Thread.sleep(retryDelay);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new IOException("重试发送命令被中断", ie);
                    }
                }
            }
        }

        if (lastException != null) {
            throw new IOException("发送命令失败，已重试" + maxRetries + "次", lastException);
        }

        return false;
    }

    /**
     * 发送命令并等待响应
     *
     * @param command Pelco D命令数据包
     * @param timeout 等待响应的超时时间（毫秒）
     * @return 接收到的响应数据，如果没有响应则返回null
     * @throws IOException 通信异常
     */
    public byte[] sendCommandAndWaitResponse(byte[] command, int timeout) throws IOException {
        int retries = 0;
        IOException lastException = null;

        while (retries <= maxRetries) {
            try {
                if (sendCommand(command)) {
                    byte[] response = receiveData(timeout);
                    if (response != null) {
                        return response;
                    }
                }

                retries++;
                if (retries <= maxRetries) {
                    try {
                        Thread.sleep(retryDelay);
                        System.out.println("正在重试发送命令，第" + retries + "次...");
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new IOException("等待响应被中断", ie);
                    }
                }
            } catch (IOException e) {
                lastException = e;

                retries++;
                if (retries <= maxRetries) {
                    try {
                        Thread.sleep(retryDelay);
                        System.out.println("发送命令异常，正在重试，第" + retries + "次...");
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new IOException("重试等待响应被中断", ie);
                    }
                }
            }
        }

        if (lastException != null) {
            throw new IOException("发送命令并等待响应失败，已重试" + maxRetries + "次", lastException);
        }

        return null;
    }

    /**
     * 发送命令并等待响应（使用默认超时时间）
     *
     * @param command Pelco D命令数据包
     * @return 接收到的响应数据，如果没有响应则返回null
     * @throws IOException 通信异常
     */
    public byte[] sendCommandAndWaitResponse(byte[] command) throws IOException {
        return sendCommandAndWaitResponse(command, readTimeout);
    }

    /**
     * 设置连接超时时间
     *
     * @param timeout 超时时间（毫秒）
     */
    public void setConnectionTimeout(int timeout) {
        this.connectionTimeout = timeout;
    }

    /**
     * 设置最大重试次数
     *
     * @param retries 重试次数
     */
    public void setMaxRetries(int retries) {
        this.maxRetries = retries;
    }

    /**
     * 设置预设位
     *
     * @param presetNumber 预设位编号 (1-255)
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean setPreset(int presetNumber) throws IOException {
        return sendCommand(PelcoDProtocol.setPreset(deviceAddress, presetNumber));
    }

    /**
     * 设置读取超时时间
     *
     * @param timeout 超时时间（毫秒）
     */
    public void setReadTimeout(int timeout) {
        this.readTimeout = timeout;
    }

    /**
     * 设置重试延迟时间
     *
     * @param delay 延迟时间（毫秒）
     */
    public void setRetryDelay(int delay) {
        this.retryDelay = delay;
    }

    /**
     * 停止云台移动
     *
     * @return 是否发送成功
     * @throws IOException 发送异常
     */
    public boolean stopMovement() throws IOException {
        return sendCommand(PelcoDProtocol.stopMovement(deviceAddress));
    }

    /**
     * 等待数据到达
     *
     * @param timeout 超时时间（毫秒）
     * @return 是否有数据可读
     */
    private boolean waitForData(int timeout) {
        try {
            long startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < timeout) {
                if (inputStream.available() > 0) {
                    return true;
                }
                // 短暂休眠，避免CPU占用过高
                Thread.sleep(10);
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }
}
